/*   The MIT License
 *   
 *   Carina Engine
 *   Copyright (c) 2009 2010 2011 2012 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#ifndef DX_LOADER_HH_
#define DX_LOADER_HH_

#include "carina/common/global.hh"

#include "carina/common/containers.hh"
#include "carina/renderer-base.hh"
#include "carina/dx-library.hh"
#include "carina/library.hh"

#include <map>
#include <iostream>

#ifdef __MINGW32__
#include <specstrings.h>

#ifndef WINAPI_INLINE
#   define WINAPI_INLINE inline
#endif

typedef unsigned char UINT8;
#endif

#include <D3DX11.h>
#include <d3dx11effect.h>

#define WIN32_LEAN_AND_MEAN 1
#include <windows.h>

namespace Carina
{
DXGI_FORMAT D3DTranslateVarType(VarType vtype);

struct DXUsageInfo
{
    UINT            CPUAccessFlags;
    D3D11_USAGE     Usage;
    DXUsageInfo() {}
    DXUsageInfo(UINT cpuacc, D3D11_USAGE u)
        :   CPUAccessFlags(cpuacc),
            Usage(u) {}
};

class DXStateObject;
typedef shared_ptr<DXStateObject> DXStateObjectPtr;

class DXBlendStateObject;
typedef shared_ptr<DXBlendStateObject> DXBlendStateObjectPtr;

class DXDepthStencilStateObject;
typedef shared_ptr<DXDepthStencilStateObject> DXDepthStencilStateObjectPtr;

class DXFramebuffer;
typedef shared_ptr<DXFramebuffer> DXFramebufferPtr;

class DXRenderer;

class HLSLShaderCompiler: public ShaderCompiler
{
    DXRenderer&                             m_Renderer;
public:
    HLSLShaderCompiler(DXRenderer& renderer);
     ~HLSLShaderCompiler();

    virtual ShaderProgramPtr compileShaderProgram(const string& filename) const;
};

class DXIncludeProxyHandler: public ID3DInclude
{
    IncludeLoaderPtr                m_IncludeLoader;
public:
    DXIncludeProxyHandler();
    virtual ~DXIncludeProxyHandler();

    HRESULT __stdcall Open(D3D_INCLUDE_TYPE, LPCSTR filename, LPCVOID, LPCVOID *data, UINT *size);
    HRESULT __stdcall Close(LPCVOID pData);
    
    void setIncludeLoader(const IncludeLoaderPtr& incl_loader);
    IncludeLoaderPtr getIncludeLoader();
};

class DXRenderer: public RendererBase
{
    ComRAII<ID3D11Device>           m_Device;
    ComRAII<ID3D11DeviceContext>    m_DeviceContext;
    ComRAII<ID3D11DepthStencilView> m_DepthStencilView;
    ComRAII<ID3D11RenderTargetView> m_RenderTargetView;
    
    ShaderProgramPtr		        m_Prog;
    DXStateObjectPtr                m_CurrentState,
                                    m_DefaultState;
    DXBlendStateObjectPtr           m_CurrentBlendState,
                                    m_DefaultBlendState;
    DXDepthStencilStateObjectPtr    m_CurrentDepthStencilState,
                                    m_DefaultDepthStencilState;

    DXFramebufferPtr                m_DrawFramebuffer,
                                    m_ReadFramebuffer;

    DXIncludeProxyHandler           m_IncludeHandler;

    Vector4                         m_ClearColor;
    ShaderCompilerPtr               m_PreferredShaderCompiler;
    DataVector<ShaderCompilerPtr>   m_ShaderCompilers;
public:
    DXRenderer(ComRAII<ID3D11Device>& dev);
    virtual ~DXRenderer();

    virtual InputLayoutPtr createInputLayout(const ShaderProgramPtr& shader, const InputElementArray& arr);
    virtual void setInputLayout(const InputLayoutConstPtr& layout);

    virtual VideoBufferPtr createVideoBuffer(VBType type, size_t size, const void* data, VBUsage usage);
    virtual void setVideoBuffer(const VideoBufferPtr& vb, size_t stride, size_t offset=0);
    virtual void setIndexBuffer(const VideoBufferPtr& vb, IndicesType itype, size_t offset=0);

    virtual void draw(DrawModes mode, size_t first, size_t count);
    virtual void drawIndexed(DrawModes mode, size_t first, size_t count);

    virtual ShaderCompilerPtr getPreferredShaderCompiler(const ShadingLanguagesList& shlang_list);
    virtual ShaderCompilerPtr getShaderCompiter(const string& shading_language);
    virtual void setPreferredShadingLanguage(const string& name);
    virtual void useShaderProgram(const ShaderProgramPtr& prog);

	virtual TexturePtr createTexture(const TextureDescription& desc, const void* data, int flags);

    virtual void resizeViewport(size_t w, size_t h);
    virtual void getViewport(size_t& w, size_t& h);

    ID3D11Device& getD3DDevice() { return *m_Device; }
    ID3D11DeviceContext& getD3DCurrentDeviceContext() { return *m_DeviceContext; }

	virtual StateObjectPtr createStateObject(const MiscStateDescription& desc);
    virtual void setStateObject(const StateObjectPtr& state_obj);
    virtual StateObjectPtr getStateObject();

    virtual void setScissorRect(size_t left, size_t top, size_t right, size_t bottom);

	virtual BlendStateObjectPtr createBlendStateObject(const BlendStateDescription& desc);
    virtual void setBlendStateObject(const BlendStateObjectPtr& bstate_obj);
    virtual BlendStateObjectPtr getBlendStateObject();

	virtual DepthStencilStateObjectPtr createDepthStencilStateObject(const DepthStencilStateDescription& desc);
    virtual void setDepthStencilStateObject(const DepthStencilStateObjectPtr& dsstate_obj);
    virtual DepthStencilStateObjectPtr getDepthStencilStateObject();

    virtual FramebufferPtr createFramebuffer();
    virtual void bindFramebuffer(const FramebufferPtr& fb);
    virtual void bindDrawFramebuffer(const FramebufferPtr& fb);
    virtual void bindReadFramebuffer(const FramebufferPtr& fb);
    virtual void blitFramebuffer(int buffers);
    virtual FramebufferPtr getDrawFramebuffer();
    virtual FramebufferPtr getReadFramebuffer();

    virtual Vector4 getClearColor() const;
    virtual void setClearColor(const Vector4& color);
    virtual void clearColorBuffer(size_t idx, const Vector4& color);
    virtual void clearDepthStencilBuffer(float depth, int stencil);
    virtual void clearAll();

    virtual void registerIncludeLoader(const IncludeLoaderPtr& include_loader);
    virtual IncludeLoaderPtr getIncludeLoader() { return m_IncludeHandler.getIncludeLoader(); }
    
    virtual Matrix4 createPerspectiveMatrix(float fovy, float aspect, float zNear, float zFar);
    virtual Matrix4 createOrthoMatrix(float left, float right, float bottom, float top, float _near=-1.0f, float _far=1.0f);

    void _updateBuffers();
    void _releaseBuffers();

    DXIncludeProxyHandler& getIncludeHandler();
};

class DXStateObject: public StateObject
{
    ComRAII<ID3D11RasterizerState>  m_RSState;
public:
    DXStateObject(DXRenderer& renderer,
				  const MiscStateDescription& desc);

    void setup(DXRenderer& renderer);
};

class DXBlendStateObject: public BlendStateObject
{
    ComRAII<ID3D11BlendState>       m_BlendState;
public:
    DXBlendStateObject(DXRenderer& renderer,
                       const BlendStateDescription& desc);

    void setup(DXRenderer& renderer);
private:
    static D3D11_BLEND getDXBlend(CEBlend blend);
    static D3D11_BLEND_OP getDXBlendOp(CEBlendOperation blendop);
};

class DXDepthStencilStateObject: public DepthStencilStateObject
{
    int m_StencilRef;

    ComRAII<ID3D11DepthStencilState> m_DepthStencilState;
public:
    DXDepthStencilStateObject(DXRenderer& renderer,
							  const DepthStencilStateDescription& desc);

    void setup(DXRenderer& renderer);
private:
    static D3D11_STENCIL_OP getStencilOp(CEStencilOperation sop);
    static D3D11_COMPARISON_FUNC getCompFunc(CECompFunc sfunc);
};

class DXInputLayout: public InputLayout
{
    ComRAII<ID3D11InputLayout> m_InputLayout;
public:
    DXInputLayout(DXRenderer& renderer, const ShaderProgramPtr& shader, const InputElementArray& arr);

    void setup(DXRenderer& rend) const;
};

class DXShaderProgram: public ShaderProgram
{
    DXRenderer&             m_Renderer;
    ComRAII<ID3DX11Effect>  m_Effect;
    ID3DX11EffectTechnique  *m_CurrentTechnique;
public:
    DXShaderProgram(DXRenderer& renderer, const string& name);

    virtual void useTechnique(const string& name);

    virtual UniformVarPtr getVariable(const string& name);
    virtual TextureVarPtr getTexture(const string& name);

    virtual void getVariableType(const string& name, UniformVarTypeDesc& desc);

    void getInputSignature(BYTE*& ptr, SIZE_T& size) const;

#ifdef CE_RELOADABLE_RESOURCES
    virtual void reload(const string& filename);
#endif

    virtual void apply() const;
};

class DXUniformVar: public UniformVar
{
    ID3DX11EffectVariable* m_Var;
public:
    DXUniformVar(ID3DX11EffectVariable* var)
        :   m_Var(var) { assert(var->IsValid()); }

    virtual void set(uint32 i) { m_Var->AsScalar()->SetInt(static_cast<int32>(i)); } // WARNING!
    virtual void set(const uint32* const arr, size_t N) { m_Var->AsScalar()->SetIntArray(const_cast<int32*>(reinterpret_cast<const int32* const>(arr)), 0, N); }// WARNING!
    virtual void set(int32 i) { m_Var->AsScalar()->SetInt(i); }
    virtual void set(const int32* const arr, size_t N) { m_Var->AsScalar()->SetIntArray(const_cast<int32*>(arr), 0, N); }
    virtual void set(const Quaternion* const arr, size_t N) { m_Var->AsVector()->SetFloatVectorArray(const_cast<float*>(arr->elem), 0, N); }
    virtual void set(const DualQuaternion* const arr, size_t N) { m_Var->AsVector()->SetFloatVectorArray(const_cast<float*>(reinterpret_cast<const float*>(arr)), 0, 2*N); }
    virtual void set(const Matrix4* const arr, size_t N) { m_Var->AsMatrix()->SetMatrixArray(const_cast<float*>(reinterpret_cast<const float*>(arr)), 0, N); }
    virtual void set(const Vector4* const arr, size_t N) { m_Var->AsVector()->SetFloatVectorArray(const_cast<float*>(arr->elem), 0, N); }
    virtual void set(const Vector3* const arr, size_t N) { m_Var->AsVector()->SetRawValue(const_cast<float*>(arr->elem), 0, N*sizeof(Vector3)); }
    virtual void set(const Vector2* const arr, size_t N) { m_Var->AsVector()->SetRawValue(const_cast<float*>(arr->elem), 0, N*sizeof(Vector2)); }
    virtual void set(const float* const arr, size_t N) { m_Var->AsScalar()->SetFloatArray(const_cast<float*>(arr), 0, N); }

    virtual void set(const Vector4& vec) { m_Var->AsVector()->SetFloatVector(const_cast<float*>(vec.elem)); }
    virtual void set(const Vector3& vec) { m_Var->AsVector()->SetRawValue(const_cast<float*>(vec.elem), 0, sizeof(Vector3)); }
    virtual void set(const Vector2& vec) { m_Var->AsVector()->SetRawValue(const_cast<float*>(vec.elem), 0, sizeof(Vector2)); }
    virtual void set(float f) { m_Var->AsScalar()->SetFloat(f); }
    virtual void set(const Matrix4& mat) { m_Var->AsMatrix()->SetMatrix(const_cast<float*>(reinterpret_cast<const float*>(&mat))); }
    virtual void set(const DualQuaternion& dq) { m_Var->AsVector()->SetFloatVectorArray(const_cast<float*>(reinterpret_cast<const float*>(&dq)), 0, 2); }
    virtual void set(const Quaternion& q) { m_Var->AsVector()->SetFloatVector(const_cast<float*>(q.elem)); }

    virtual void get(uint32& integer) const { m_Var->AsScalar()->GetInt(&reinterpret_cast<int32&>(integer)); } // WARNING!
    virtual void get(uint32* const arr, size_t N) const { m_Var->AsScalar()->GetIntArray(reinterpret_cast<int32*>(arr), 0, N); }
    virtual void get(int32& integer) const { m_Var->AsScalar()->GetInt(&integer); }
    virtual void get(int32* const arr, size_t N) const { m_Var->AsScalar()->GetIntArray(arr, 0, N); }
    virtual void get(Quaternion* const arr, size_t N) const { m_Var->AsVector()->GetFloatVectorArray(reinterpret_cast<float*>(arr), 0, N); }
    virtual void get(DualQuaternion* const arr, size_t N) const { m_Var->AsVector()->GetFloatVectorArray(reinterpret_cast<float*>(arr), 0, 2*N); }
    virtual void get(Matrix4* const arr, size_t N) const { m_Var->AsMatrix()->GetMatrixArray(reinterpret_cast<float*>(arr), 0, N); }
    virtual void get(Vector4* const arr, size_t N) const { m_Var->AsVector()->GetFloatVectorArray(reinterpret_cast<float*>(arr), 0, N); }
    virtual void get(Vector3* const arr, size_t N) const { m_Var->AsVector()->GetRawValue(reinterpret_cast<float*>(arr), 0, N*sizeof(Vector3)); }
    virtual void get(Vector2* const arr, size_t N) const { m_Var->AsVector()->GetRawValue(reinterpret_cast<float*>(arr), 0, N*sizeof(Vector2)); }
    virtual void get(float* const arr, size_t N) const { m_Var->AsScalar()->GetFloatArray(arr, 0, N); }

    virtual void get(Vector4& vec) const { m_Var->AsVector()->GetFloatVector(reinterpret_cast<float*>(&vec)); }
    virtual void get(Vector3& vec) const { m_Var->AsVector()->GetRawValue(reinterpret_cast<float*>(&vec), 0, sizeof(Vector3)); }
    virtual void get(Vector2& vec) const { m_Var->AsVector()->GetRawValue(reinterpret_cast<float*>(&vec), 0, sizeof(Vector2)); }
    virtual void get(float& f) const { m_Var->AsScalar()->GetFloat(reinterpret_cast<float*>(&f)); }
    virtual void get(Matrix4& mat) const { m_Var->AsMatrix()->GetMatrix(reinterpret_cast<float*>(&mat)); }
    virtual void get(DualQuaternion& dq) const { m_Var->AsVector()->GetFloatVectorArray(reinterpret_cast<float*>(&dq), 0, 2); }
    virtual void get(Quaternion& q) const { m_Var->AsVector()->GetFloatVector(reinterpret_cast<float*>(&q)); }
};

class DXTexture: public Texture
{
    ComRAII<ID3D11Resource>             m_Texture;
    ComRAII<ID3D11ShaderResourceView>   m_ResourceView;
public:
	DXTexture(DXRenderer& rend, const TextureDescription& desc, const void* data, int flags);

    ComRAII<ID3D11Resource> getTexture() { return m_Texture; }
    ComRAII<ID3D11ShaderResourceView> getResourceView() { return m_ResourceView; }
    ComRAII<ID3D11DepthStencilView> createDepthStencilView(DXRenderer& renderer, size_t level, size_t layer);
    ComRAII<ID3D11RenderTargetView> createRenderTargetView(DXRenderer& renderer, size_t level, size_t layer);
};

typedef shared_ptr<DXTexture> DXTexturePtr;
typedef shared_ptr<const DXTexture> DXTextureConstPtr;

class DXTextureVar: public TextureVar
{
    ID3DX11EffectShaderResourceVariable* m_Var;
public:
    DXTextureVar(ID3DX11EffectShaderResourceVariable* var)
        :   m_Var(var) { assert(var->IsValid()); }

    virtual void set(const TexturePtr& tex) { m_Var->SetResource(static_cast<DXTexture&>(*tex).getResourceView()); }
    virtual void set(TexturePtr* arr, size_t N);
    virtual void reset() { m_Var->SetResource(0); }
};

class DXVideoBuffer: public VideoBuffer
{
    ComRAII<ID3D11Buffer>   m_Buffer;
public:
    DXVideoBuffer(DXRenderer& renderer, VBType type, size_t size, const void* data, VBUsage usage);

// TODO: BAAAAAAD!
    void bindVideoBuffer(DXRenderer& renderer, size_t stride, size_t offset) const;
    void bindIndexBuffer(DXRenderer& renderer, DXGI_FORMAT format, size_t offset) const;
};

class DXFramebuffer: public Framebuffer
{
    DXRenderer&                                     m_Renderer;
    DynamicArray<DXTexturePtr>                      m_Textures;
    DynamicArray<ComRAII<ID3D11RenderTargetView> >  m_TextureViews;
    DXTexturePtr                                    m_DepthStencilTexture;
    ComRAII<ID3D11DepthStencilView>                 m_DepthStencil;
public:
    DXFramebuffer(DXRenderer& renderer);

    void clearDepthStencilBuffer(float depth, int stencil);
    void clearBuffer(size_t idx, const Vector4& color);
    void clearAll(const Vector4& color);

    void bind();

    DXTexturePtr getColorAttachment(size_t idx) { return m_Textures[idx]; }
    size_t getColorAttachmentCount() const { return m_Textures.size(); }
    DXTexturePtr getDepthStencil() { return m_DepthStencilTexture; }

    virtual void attachDepthStencilTexture(const FBAttachmentInfo& texture);
    virtual void attachTextures(const FBAttachmentArray& textures);

    virtual bool validate();
};
}

#endif /* DX_LOADER_HH_ */
